#ifndef UNIX
# define GLEW_STATIC 1
#endif

#include <GL/glew.h>

#include <SDL/SDL.h>

#include <stdlib.h>

#include "../../log.h"

#include "../host.h"

#define xres 480
#define yres 272

#define N_VERTS 4
#define N_TEXCOORD 2
#define N_VERTCOORD 3

#define BUFFER_OFFSET(a) ((char*)NULL + (a))

#define TSIZE_X ((float)xres / (float)bufferWidth)
#define TSIZE_Y (1.0f)

GLuint backbufTex, pbo, fbo, renderbufcol, renderbufdep, vbo;
GLint texFormat, texType;
u32 bufferWidth = 0;
u8 fbIsSet = 0;
Timer fpsTimer;
u32 numFrames = 0;

float fbVertCoords[N_VERTS * N_VERTCOORD] =
{
    0,    0,    0,
    xres, 0,    0,
    xres, yres, 0,
    0,    yres, 0
};

float fbTexCoords[N_VERTS * N_TEXCOORD] =
{
    0, 1,
    1, 1,
    1, 0,
    0, 0
};

void host_dispInit()
{
    glViewport(0, 0, xres, yres);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(0.0f, xres, yres, 0.0f, -1.0f, 1.0f);
    glPushMatrix();

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    glActiveTexture(GL_TEXTURE1);

    glEnable(GL_TEXTURE_2D);

    /* Framebuffer (from PSP memory) drawn texture */
    glGenTextures(1, &backbufTex);

    glBindTexture(GL_TEXTURE_2D, backbufTex);
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

    glDisable(GL_TEXTURE_2D);

    glActiveTexture(GL_TEXTURE0);

    /* The framebuffer object renderbuffer for the color buffer */
    glGenRenderbuffers(1, &renderbufcol);
    glBindRenderbuffer(GL_RENDERBUFFER, renderbufcol);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA, xres, yres);

    /* The framebuffer object renderbuffer for the depth buffer */
    glGenRenderbuffers(1, &renderbufdep);
    glBindRenderbuffer(GL_RENDERBUFFER, renderbufdep);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, xres, yres);

    /* New framebuffer object, used to draw and then copy in the framebuffer virtual memory (to keep real CLEAR, etc.) */
    glGenFramebuffers(1, &fbo);
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderbufcol);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, renderbufdep);

    /* Pixel Buffer Object for the framebuffer */
    glGenBuffers(1, &pbo);

    /* Vertex Buffer Object for the framebuffer rendering */
    glGenBuffers(1, &vbo);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER,
                 (N_VERTS * N_VERTCOORD) * sizeof(*fbVertCoords) +
                 (N_VERTS * N_TEXCOORD ) * sizeof(*fbTexCoords),
                 NULL,
                 GL_STATIC_DRAW);
    glBufferSubData(GL_ARRAY_BUFFER,
                    0,
                    (N_VERTS * N_VERTCOORD) * sizeof(*fbVertCoords),
                    fbVertCoords);
    glBufferSubData(GL_ARRAY_BUFFER,
                    (N_VERTS * N_VERTCOORD) * sizeof(*fbVertCoords),
                    (N_VERTS * N_TEXCOORD ) * sizeof(*fbTexCoords),
                    fbTexCoords);
    glBindBuffer(GL_ARRAY_BUFFER, 0);

    fpsTimer = host_initTimer(1000, 1);
}

void host_dispFree()
{
    glDeleteTextures(1, &backbufTex);
    glDeleteFramebuffers(1, &fbo);
    glDeleteRenderbuffers(1, &renderbufcol);
    glDeleteRenderbuffers(1, &renderbufdep);
    glDeleteBuffers(1, &pbo);
    glDeleteBuffers(1, &vbo);
}

u8 host_fbIsSet()
{
    return fbIsSet;
}

void host_setFb(u32 lineSize, ScePixelFormat pixelSize, u32 allocSize)
{
    switch (pixelSize)
    {
    case SCE_PIXELFORMAT_565:
        texFormat = GL_RGB;
        texType = GL_UNSIGNED_SHORT_5_6_5;
        break;

    case SCE_PIXELFORMAT_5551:
        texFormat = GL_RGBA;
        texType = GL_UNSIGNED_SHORT_5_5_5_1;
        break;

    case SCE_PIXELFORMAT_4444:
        texFormat = GL_RGBA;
        texType = GL_UNSIGNED_SHORT_4_4_4_4;
        break;

    case SCE_PIXELFORMAT_8888:
        texFormat = GL_RGBA;
        texType = GL_UNSIGNED_INT_8_8_8_8;
        break;
    }

    bufferWidth = lineSize;

    glActiveTexture(GL_TEXTURE1);

    glEnable(GL_TEXTURE_2D);

    glBindTexture(GL_TEXTURE_2D, backbufTex);
    glTexImage2D(GL_TEXTURE_2D, 0, texFormat, lineSize, yres, 0, texFormat, texType, NULL);

    glDisable(GL_TEXTURE_2D);

    glActiveTexture(GL_TEXTURE0);

    glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo);
    glBufferData(GL_PIXEL_PACK_BUFFER, allocSize, NULL, GL_STREAM_READ);
    glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);

    if ((fbTexCoords[2] != TSIZE_X) || (fbTexCoords[1] != TSIZE_Y))
    {
        fbTexCoords[1] = TSIZE_Y;
        fbTexCoords[2] = TSIZE_X;
        fbTexCoords[3] = TSIZE_Y;
        fbTexCoords[4] = TSIZE_X;

        glBindBuffer(GL_ARRAY_BUFFER, vbo);
        glBufferSubData(GL_ARRAY_BUFFER,
                        (N_VERTS * N_VERTCOORD) * sizeof(*fbVertCoords),
                        (N_VERTS * N_TEXCOORD ) * sizeof(*fbTexCoords),
                        fbTexCoords);
        glBindBuffer(GL_ARRAY_BUFFER, 0);
    }

    fbIsSet = 1;
}

void host_syncFb(unsigned char *realFB)
{
    glBindFramebuffer(GL_FRAMEBUFFER, 0);

    glMatrixMode(GL_PROJECTION);
    glPopMatrix();

    glMatrixMode(GL_MODELVIEW);

    glActiveTexture(GL_TEXTURE1);

    glEnable(GL_TEXTURE_2D);

    glBindTexture(GL_TEXTURE_2D, backbufTex);
    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, xres, yres, texFormat, texType, realFB);

    glBindBuffer(GL_ARRAY_BUFFER, vbo);

    glClientActiveTexture(GL_TEXTURE1);
    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);

    glVertexPointer(N_VERTCOORD, GL_FLOAT, 0, BUFFER_OFFSET(0));
    glTexCoordPointer(N_TEXCOORD, GL_FLOAT, 0, BUFFER_OFFSET(N_VERTS * N_VERTCOORD * sizeof(*fbVertCoords)));

    glDrawArrays(GL_TRIANGLE_FAN, 0, 4);

    glDisableClientState(GL_VERTEX_ARRAY);
    glDisableClientState(GL_TEXTURE_COORD_ARRAY);
    glClientActiveTexture(GL_TEXTURE0);

    glBindBuffer(GL_ARRAY_BUFFER, 0);

    glDisable(GL_TEXTURE_2D);

    glActiveTexture(GL_TEXTURE0);

    glMatrixMode(GL_PROJECTION);
    glPushMatrix();

    host_frameFinish();

    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
}

void host_dispFinish()
{
    glFinish();
}

void *host_initScenePtr()
{
    glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo);
    glReadPixels(0, 0, xres, yres, texFormat, texType, NULL);
    return glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY);
}

void host_freeScenePtr()
{
    glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
    glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
}

void host_nextFrame()
{
    numFrames++;
    if (host_testTimer(&fpsTimer))
    {
        u32 i;
        u32 frames = numFrames;
        char *string;
        /* Determine the number of digits in the FPS, to store it in a string */
        for (i = 0; frames >= 1; i++)
            frames /= 10;
        string = malloc(sizeof(char) * (i + 5));
        string[0] = '\0';
        sprintf(string, "%d FPS", numFrames);
        host_setWinTitle(string);
        numFrames = 0;
        free(string);
    }
}

